home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-02 / commontp.zip / IBMCOM.ZIP / IBMCOM.PAS next >
Pascal/Delphi Source File  |  1990-06-21  |  13KB  |  539 lines

  1.  
  2. CONST
  3.   max_port = 4;
  4.  
  5.  
  6. {Base i/o address for each COM port}
  7.  
  8. CONST
  9.   uart_base: ARRAY [1..max_port] OF Integer = ($3F8, $2F8, $3E8, $2E8);
  10.  
  11.  
  12. {Interrupt numbers for each COM port}
  13.  
  14. CONST
  15.   intnums: ARRAY [1..max_port] OF Byte = ($0C, $0B, $0C, $0B);
  16.  
  17.  
  18. {i8259 interrupt levels for each port}
  19.  
  20. CONST
  21.   i8259levels: ARRAY [1..max_port] OF Byte = (4, 3, 4, 3);
  22.  
  23.  
  24. {This variable is TRUE if the interrupt driver has been installed, or FALSE
  25. if it hasn't.  It's used to prevent installing twice or deinstalling when not
  26. installed.}
  27.  
  28. CONST
  29.   com_installed: Boolean = False;
  30.  
  31.  
  32. {UART i/o addresses.  Values depend upon which COMM port is selected.}
  33.  
  34. VAR
  35.   uart_data: Word;             {Data register}
  36.   uart_ier : Word;             {Interrupt enable register}
  37.   uart_iir : Word;             {Interrupt identification register}
  38.   uart_lcr : Word;             {Line control register}
  39.   uart_mcr : Word;             {Modem control register}
  40.   uart_lsr : Word;             {Line status register}
  41.   uart_msr : Word;             {Modem status register}
  42.   uart_spr : Word;             {Scratch pad register}
  43.  
  44.  
  45. {Original contents of IER and MCR registers.  Used to restore UART
  46. to whatever state it was in before this driver was loaded.}
  47.  
  48. VAR
  49.   old_ier: Byte;
  50.   old_mcr: Byte;
  51.  
  52.  
  53. {Original contents of interrupt vector.  Used to restore the vector when
  54. the interrupt driver is deinstalled.}
  55.  
  56. VAR
  57.   old_vector: Pointer;
  58.  
  59.  
  60. {Original contents of interrupt controller mask.  Used to restore the
  61. bit pertaining to the comm controller we're using.}
  62.  
  63. VAR
  64.   old_i8259_mask: Byte;
  65.  
  66.  
  67. {Bit mask for i8259 interrupt controller}
  68.  
  69. VAR
  70.   i8259bit: Byte;
  71.  
  72.  
  73. {Interrupt vector number}
  74.  
  75. VAR
  76.   intnum: Byte;
  77.  
  78.  
  79. {Receive queue.  Received characters are held here until retrieved by
  80. com_rx.}
  81.  
  82. CONST
  83.   rx_queue_size = 128;   {Change to suit}
  84. VAR
  85.   rx_queue: ARRAY [1..rx_queue_size] OF Byte;
  86.   rx_in   : Word;        {Index of where to store next character}
  87.   rx_out  : Word;        {Index of where to retrieve next character}
  88.   rx_chars: Word;        {Number of chars in queue}
  89.  
  90.  
  91. {Transmit queue.  Characters to be transmitted are held here until the
  92. UART is ready to transmit them.}
  93.  
  94. CONST
  95.   tx_queue_size = 16;    {Change to suit}
  96. VAR
  97.   tx_queue: ARRAY [1..tx_queue_size] OF Byte;
  98.   tx_in   : Integer;     {Index of where to store next character}
  99.   tx_out  : Integer;     {Index of where to retrieve next character}
  100.   tx_chars: integer;     {Number of chars in queue}
  101.  
  102.  
  103. {This variable is used to save the next link in the "exit procedure" chain.}
  104.  
  105. VAR
  106.   exit_save: Pointer;
  107.  
  108.  
  109. {$I ints.inc}   {Macros for enabling and disabling interrupts}
  110.  
  111.  
  112. {Interrupt driver.  The UART is programmed to cause an interrupt whenever
  113. a character has been received or when the UART is ready to transmit another
  114. character.}
  115.  
  116. {$R-,S-}
  117. PROCEDURE com_interrupt_driver; INTERRUPT;
  118.  
  119. VAR
  120.   ch   : Char;
  121.   iir  : Byte;
  122.   dummy: Byte;
  123.  
  124. BEGIN
  125.  
  126.   {While bit 0 of the interrupt identification register is 0, there is an
  127.   interrupt to process}
  128.  
  129.   iir := Port [uart_iir];
  130.  
  131.   WHILE NOT Odd (iir) DO
  132.     BEGIN
  133.  
  134.     CASE iir SHR 1 OF
  135.  
  136.       {iir = 100b: Received data available.  Get the character, and if
  137.       the buffer isn't full, then save it.  If the buffer is full,
  138.       then ignore it.}
  139.  
  140.       2:
  141.         BEGIN
  142.         ch := Char (Port [uart_data] );
  143.         IF (rx_chars <= rx_queue_size) THEN
  144.           BEGIN
  145.           rx_queue [rx_in] := Ord (ch);
  146.           Inc (rx_in);
  147.           IF rx_in > rx_queue_size THEN
  148.             rx_in := 1;
  149.           rx_chars := Succ (rx_chars);
  150.           END;
  151.         END;
  152.  
  153.       {iir = 010b: Transmit register empty.  If the transmit buffer
  154.       is empty, then disable the transmitter to prevent any more
  155.       transmit interrupts.  Otherwise, send the character.
  156.  
  157.       The test of the line-status-register is to see if the transmit
  158.       holding register is truly empty.  Some UARTS seem to cause transmit
  159.       interrupts when the holding register isn't empty, causing transmitted
  160.       characters to be lost.}
  161.  
  162.       1:
  163.         IF (tx_chars <= 0) THEN
  164.           Port [uart_ier] := Port [uart_ier] AND NOT 2
  165.         ELSE
  166.           IF Odd (Port [uart_lsr] SHR 5) THEN
  167.             BEGIN
  168.             Port [uart_data] := tx_queue [tx_out];
  169.             Inc (tx_out);
  170.             IF tx_out > tx_queue_size THEN
  171.               tx_out := 1;
  172.             Dec (tx_chars);
  173.             END;
  174.  
  175.       {iir = 001b: Change in modem status.  We don't expect this interrupt,
  176.       but if one ever occurs we need to read the line status to reset it
  177.       and prevent an endless loop.}
  178.  
  179.       0:
  180.         dummy := Port [uart_msr];
  181.  
  182.       {iir = 111b: Change in line status.  We don't expect this interrupt,
  183.       but if one ever occurs we need to read the line status to reset it
  184.       and prevent an endless loop.}
  185.  
  186.       3:
  187.         dummy := Port [uart_lsr];
  188.  
  189.       END;
  190.  
  191.     iir := Port [uart_iir];
  192.     END;
  193.  
  194.   {Tell the interrupt controller that we're done with this interrupt}
  195.  
  196.   Port [$20] := $20;
  197.  
  198. END;
  199. {$R+,S+}
  200.  
  201.  
  202. {Flush (empty) the receive buffer.}
  203.  
  204. PROCEDURE com_flush_rx;
  205. BEGIN
  206.   disable_interrupts;
  207.   rx_chars := 0;
  208.   rx_in    := 1;
  209.   rx_out   := 1;
  210.   enable_interrupts;
  211. END;
  212.  
  213.  
  214. {Flush (empty) transmit buffer.}
  215.  
  216. PROCEDURE com_flush_tx;
  217. BEGIN
  218.   disable_interrupts;
  219.   tx_chars := 0;
  220.   tx_in    := 1;
  221.   tx_out   := 1;
  222.   enable_interrupts;
  223. END;
  224.  
  225.  
  226. {This function returns TRUE if a carrier is present.}
  227.  
  228. FUNCTION com_carrier: Boolean;
  229. BEGIN
  230.   com_carrier := com_installed AND Odd (Port [uart_msr] SHR 7);
  231. END;
  232.  
  233.  
  234. {Get a character from the receive buffer.  If the buffer is empty, return
  235. a NULL (#0).}
  236.  
  237. FUNCTION com_rx: Char;
  238. BEGIN
  239.   IF NOT com_installed OR (rx_chars = 0) THEN
  240.     com_rx := #0
  241.   ELSE
  242.     BEGIN
  243.     disable_interrupts;
  244.     com_rx := Chr (rx_queue [rx_out] );
  245.     Inc (rx_out);
  246.     IF rx_out > rx_queue_size THEN
  247.       rx_out := 1;
  248.     Dec (rx_chars);
  249.     enable_interrupts;
  250.     END;
  251. END;
  252.  
  253.  
  254. {This function returns True if com_tx can accept a character.}
  255.  
  256. FUNCTION com_tx_ready: Boolean;
  257. BEGIN
  258.   com_tx_ready := (tx_chars < tx_queue_size) OR NOT com_installed;
  259. END;
  260.  
  261.  
  262. {This function returns True if the transmit buffer is empty.}
  263.  
  264. FUNCTION com_tx_empty: Boolean;
  265. BEGIN
  266.   com_tx_empty := (tx_chars = 0) OR NOT com_installed;
  267. END;
  268.  
  269.  
  270. {This function returns True if the receive buffer is empty.}
  271.  
  272. FUNCTION com_rx_empty: Boolean;
  273. BEGIN
  274.   com_rx_empty := (rx_chars = 0) OR NOT com_installed;
  275. END;
  276.  
  277.  
  278. {Send a character.  Waits until the transmit buffer isn't full, then puts
  279. the character into it.  The interrupt driver will send the character
  280. once the character is at the head of the transmit queue and a transmit
  281. interrupt occurs.}
  282.  
  283. PROCEDURE com_tx (ch: Char);
  284. BEGIN
  285.   IF com_installed THEN
  286.     BEGIN
  287.     REPEAT UNTIL com_tx_ready;
  288.     disable_interrupts;
  289.     tx_queue [tx_in] := Ord (ch);
  290.     IF tx_in < tx_queue_size THEN
  291.       Inc (tx_in)
  292.     ELSE
  293.       tx_in := 1;
  294.     Inc (tx_chars);
  295.     Port [uart_ier] := Port [uart_ier] OR 2;
  296.     enable_interrupts;
  297.     END;
  298. END;
  299.  
  300.  
  301. {Send a whole string}
  302.  
  303. PROCEDURE com_tx_string (st: String);
  304. VAR
  305.   i: Byte;
  306. BEGIN
  307.   FOR i := 1 TO Length (st) DO
  308.     com_tx (st [i] );
  309. END;
  310.  
  311.  
  312. {Lower (deactivate) the DTR line.  Causes most modems to hang up.}
  313.  
  314. PROCEDURE com_lower_dtr;
  315. BEGIN
  316.   IF com_installed THEN
  317.     BEGIN
  318.     disable_interrupts;
  319.     Port [uart_mcr] := Port [uart_mcr] AND NOT 1;
  320.     enable_interrupts;
  321.     END;
  322. END;
  323.  
  324.  
  325. {Raise (activate) the DTR line.}
  326.  
  327. PROCEDURE com_raise_dtr;
  328. BEGIN
  329.   IF com_installed THEN
  330.     BEGIN
  331.     disable_interrupts;
  332.     Port [uart_mcr] := Port [uart_mcr] OR 1;
  333.     enable_interrupts;
  334.     END;
  335. END;
  336.  
  337.  
  338. {Set the baud rate.  Accepts any speed between 2 and 65535.  However,
  339. I am not sure that extremely high speeds (those above 19200) will
  340. always work, since the baud rate divisor will be six or less, where a
  341. difference of one can represent a difference in baud rate of
  342. 3840 bits per second or more.}
  343.  
  344. PROCEDURE com_set_speed (speed: Word);
  345. VAR
  346.   divisor: Word;
  347. BEGIN
  348.   IF com_installed THEN
  349.     BEGIN
  350.     IF speed < 2 THEN speed := 2;
  351.     divisor := 115200 DIV speed;
  352.     disable_interrupts;
  353.     Port  [uart_lcr]  := Port [uart_lcr] OR $80;
  354.     Portw [uart_data] := divisor;
  355.     Port  [uart_lcr]  := Port [uart_lcr] AND NOT $80;
  356.     enable_interrupts;
  357.     END;
  358. END;
  359.  
  360.  
  361. {Set the parity and stop bits as follows:
  362.  
  363.   com_none    8 data bits, no parity
  364.   com_even    7 data bits, even parity
  365.   com_odd     7 data bits, odd parity
  366.   com_zero    7 data bits, parity always zero
  367.   com_one     7 data bits, parity always one}
  368.  
  369. PROCEDURE com_set_parity (parity: com_parity; stop_bits: Byte);
  370. VAR
  371.   lcr: Byte;
  372. BEGIN
  373.   CASE parity OF
  374.     com_none: lcr := $00 OR $03;
  375.     com_even: lcr := $18 OR $02;
  376.     com_odd : lcr := $08 OR $02;
  377.     com_zero: lcr := $38 OR $02;
  378.     com_one : lcr := $28 OR $02;
  379.     END;
  380.   IF stop_bits = 2 THEN
  381.     lcr := lcr OR $04;
  382.   disable_interrupts;
  383.   Port [uart_lcr] := Port [uart_lcr] AND $40 OR lcr;
  384.   enable_interrupts;
  385. END;
  386.  
  387. {Install the communications driver.  Portnum should be 1..max_port.
  388. Error codes returned are:
  389.  
  390.   0 - No error
  391.   1 - Invalid port number
  392.   2 - UART for that port is not present
  393.   3 - Already installed, new installation ignored}
  394.  
  395. PROCEDURE com_install
  396.   (
  397.   portnum  : Word;
  398.   VAR error: Word
  399.   );
  400. VAR
  401.   ier: Byte;
  402. BEGIN
  403.   IF com_installed THEN
  404.     error := 3
  405.   ELSE
  406.     IF (portnum < 1) OR (portnum > max_port) THEN
  407.       error := 1
  408.     ELSE
  409.       BEGIN
  410.  
  411.       {Set i/o addresses and other hardware specifics for selected port}
  412.  
  413.       uart_data := uart_base [portnum];
  414.       uart_ier  := uart_data + 1;
  415.       uart_iir  := uart_data + 2;
  416.       uart_lcr  := uart_data + 3;
  417.       uart_mcr  := uart_data + 4;
  418.       uart_lsr  := uart_data + 5;
  419.       uart_msr  := uart_data + 6;
  420.       uart_spr  := uart_data + 7;
  421.       intnum    := intnums [portnum];
  422.       i8259bit  := 1 SHL i8259levels [portnum];
  423.  
  424.       {Return error if hardware not installed}
  425.  
  426.       old_ier := Port [uart_ier];
  427.       Port [uart_ier] := 0;
  428.       IF Port [uart_ier] <> 0 THEN
  429.         error := 2
  430.       ELSE
  431.         BEGIN
  432.         error := 0;
  433.  
  434.         {Save original interrupt controller mask, then disable the
  435.         interrupt controller for this interrupt.}
  436.  
  437.         disable_interrupts;
  438.         old_i8259_mask := Port [$21];
  439.         Port [$21] := old_i8259_mask OR i8259bit;
  440.         enable_interrupts;
  441.  
  442.         {Clear the transmit and receive queues}
  443.  
  444.         com_flush_tx;
  445.         com_flush_rx;
  446.  
  447.         {Save current interrupt vector, then set the interrupt vector to
  448.         the address of our interrupt driver.}
  449.  
  450.         GetIntVec (intnum, old_vector);
  451.         SetIntVec (intnum, @com_interrupt_driver);
  452.         com_installed := True;
  453.  
  454.         {Set parity to none, turn off BREAK signal, and make sure
  455.         we're not addressing the baud rate registers.}
  456.  
  457.         Port [uart_lcr] := 3;
  458.  
  459.         {Save original contents of modem control register, then enable
  460.         interrupts to system bus and activate RTS.  Leave DTR the way
  461.         it was.}
  462.  
  463.         disable_interrupts;
  464.         old_mcr := Port [uart_mcr];
  465.         Port [uart_mcr] := $A OR (old_mcr AND 1);
  466.         enable_interrupts;
  467.  
  468.         {Enable interrupt on data-available.  The interrupt for
  469.         transmit-ready is enabled when a character is put into the
  470.         transmit queue, and disabled when the transmit queue is empty.}
  471.  
  472.         Port [uart_ier] := 1;
  473.  
  474.         {Enable the interrupt controller for this interrupt.}
  475.  
  476.         disable_interrupts;
  477.         Port [$21] := Port [$21] AND NOT i8259bit;
  478.         enable_interrupts;
  479.  
  480.         END;
  481.       END;
  482. END;
  483.  
  484.  
  485. {Deinstall the interrupt driver completely.  It doesn't change the baud rate
  486. or mess with DTR; it tries to leave the interrupt vectors and enables and
  487. everything else as it was when the driver was installed.
  488.  
  489. This procedure MUST be called by the exit procedure of this module before
  490. the program exits to DOS, or the interrupt driver will still
  491. be attached to its vector -- the next communications interrupt that came
  492. along would jump to the interrupt driver which is no longer protected and
  493. may have been written over.}
  494.  
  495.  
  496. PROCEDURE com_deinstall;
  497. BEGIN
  498.   IF com_installed THEN
  499.     BEGIN
  500.  
  501.     com_installed := False;
  502.  
  503.     {Restore Modem-Control-Register and Interrupt-Enable-Register.}
  504.  
  505.     Port [uart_mcr] := old_mcr;
  506.     Port [uart_ier] := old_ier;
  507.  
  508.     {Restore appropriate bit of interrupt controller's mask}
  509.  
  510.     disable_interrupts;
  511.     Port [$21] := Port [$21] AND NOT i8259bit OR
  512.      old_i8259_mask AND i8259bit;
  513.     enable_interrupts;
  514.  
  515.     {Reset the interrupt vector}
  516.  
  517.     SetIntVec (intnum, old_vector);
  518.  
  519.     END;
  520. END;
  521.  
  522.  
  523. {This procedure is called when the program exits for any reason.  It
  524. deinstalls the interrupt driver.}
  525.  
  526. {$F+} PROCEDURE exit_procedure; {$F-}
  527. BEGIN
  528.   com_deinstall;
  529.   ExitProc := exit_save;
  530. END;
  531.  
  532.  
  533. {This installs the exit procedure.}
  534.  
  535. BEGIN
  536.   exit_save := ExitProc;
  537.   ExitProc := @exit_procedure;
  538. END.
  539.